//Steve Hipolito, Daniel Beraun - Project 1

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.util.ArrayList;

import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SwingConstants;

/**
 * GUI for Web Crawler. Handles both single and multi threading.
 * 
 * @author Steve Hipolito
 *
 */
@SuppressWarnings("serial")
public class WebCrawlerGUI extends JFrame {
	private volatile PageAnalyzer pa;
	private volatile PageRetriever pr;
	private volatile RunnablePageAnalyzer rpa;
	private volatile RunnablePageRetriever rpr;
	private volatile RunnablePageParser rpp;
	private int pageLimit = 15;
	private ArrayList<String> keywords = new ArrayList<String>();
	private String url;
	
	private JPanel west = new JPanel(new GridLayout(14, 1));
	private	JTextField urlTF = new JTextField(40);
	private ButtonGroup threadRadioButtons = new ButtonGroup();
	private	JTextField keywordTF = new JTextField(10);
	private	JTextField pageLimitTF = new JTextField(2);
	private JTextArea statsTA = new JTextArea();
	private JScrollPane statsSP = new JScrollPane(statsTA);
	private JTextArea hitsTA = new JTextArea();
	private JScrollPane hitsSP = new JScrollPane(hitsTA);
	private boolean multiThreadCrawling = false;

	public WebCrawlerGUI() throws IOException {
		super("Web Crawler");
	}
	
	public void start() {
		setPreferredSize(new Dimension(700, 550));
	    setDefaultCloseOperation(EXIT_ON_CLOSE);
	    setResizable(false);
	    components();
		pack();
		setVisible(true);
		
		urlTF.setText("http://css.insttech.washington.edu/~mealden/");
		//urlTF.setText("http://www.google.com/");
	}
	
	private void components() {
		keywordTF.addActionListener(new ActionListener() {
			public void actionPerformed(final ActionEvent the_event) {
				addkeyword();
			}
		});
		
		//north
		
		JPanel north = new JPanel();
		
		JPanel north_left = new JPanel();
		JPanel north_center = new JPanel(new GridLayout(2, 1));
		JPanel north_right = new JPanel();
		
		north_left.add(new JLabel("URL: "), BorderLayout.WEST);
		north_left.add(urlTF);
		north_center.add(createThreadRadioButton("Single-Thread", true));
		north_center.add(createThreadRadioButton("Multi-Thread", false));

		north_right.add(createButton("CRAWL"));
		
		north.add(north_left, BorderLayout.WEST);
		north.add(north_center, BorderLayout.CENTER);
		north.add(north_right, BorderLayout.EAST);
		
		//west
		
		JPanel keywordTFContainer = new JPanel();
		JPanel west_buttons = new JPanel();
		
		keywordTFContainer.add(keywordTF);
		
		west_buttons.add(createButton("ADD"), BorderLayout.WEST);
		west_buttons.add(createButton("CLEAR"), BorderLayout.EAST);
		
		JPanel pageLimitTFContainer = new JPanel();
		pageLimitTFContainer.add(new JLabel("Pages to Crawl:"), BorderLayout.WEST);
		pageLimitTF.setText(""+pageLimit);
		pageLimitTFContainer.add(pageLimitTF);
		
		west.add(pageLimitTFContainer);
		west.add(new JLabel("Keywords", SwingConstants.CENTER));
		west.add(keywordTFContainer);
		west.add(west_buttons);
		
		//center
		
		statsTA.setEditable(false);
		statsSP.setPreferredSize(new Dimension(250, 445));
		hitsTA.setEditable(false);
		hitsSP.setPreferredSize(new Dimension(250, 445));
		
		JPanel center = new JPanel(new FlowLayout());
		center.add(statsSP);
		center.add(hitsSP);
		
		//add main components to frame
		
		add(north, BorderLayout.NORTH);
		add(west, BorderLayout.WEST);
		add(center, BorderLayout.CENTER);
	}
	
	private void clear_west() {
		west.removeAll();
		
		JPanel keywordTFContainer = new JPanel();
		JPanel west_buttons = new JPanel();
		
		keywordTFContainer.add(keywordTF);
		
		west_buttons.add(createButton("ADD"), BorderLayout.WEST);
		west_buttons.add(createButton("CLEAR"), BorderLayout.EAST);
		
		JPanel pageLimitTFContainer = new JPanel();
		pageLimitTFContainer.add(new JLabel("Pages to Crawl:"), BorderLayout.WEST);
		pageLimitTF.setText(""+pageLimit);
		pageLimitTFContainer.add(pageLimitTF);
		
		west.add(pageLimitTFContainer);
		west.add(new JLabel("Keywords", SwingConstants.CENTER));
		west.add(keywordTFContainer);
		west.add(west_buttons);
		
		repaint();
		validate();
	}
	
	private void addkeyword() {
		if (!keywordTF.getText().trim().equalsIgnoreCase("") &&
				keywords.size() < 9) {
			west.add(new JLabel("   "+keywordTF.getText().trim()));
			keywords.add(keywordTF.getText().trim());
			keywordTF.setText("");
			keywordTF.requestFocus();
			west.repaint();
			west.revalidate();
		} else if (!keywordTF.getText().trim().equalsIgnoreCase("") &&
				keywords.size() == 9) {
			west.add(new JLabel("   "+keywordTF.getText().trim()));
			keywords.add(keywordTF.getText().trim());
			west.repaint();
			west.revalidate();
			keywordTF.setText("press CLEAR");
			keywordTF.setEnabled(false);
		}
	}
	
	private JRadioButton createThreadRadioButton(final String the_button, final boolean selected) {
		final JRadioButton button = new JRadioButton(the_button);
		button.addActionListener(new ActionListener() {
			public void actionPerformed(final ActionEvent the_event) {
				if (the_button.equalsIgnoreCase("Single-Thread")) {
					multiThreadCrawling = false;
				} else if (the_button.equalsIgnoreCase("Multi-Thread")) {
					multiThreadCrawling = true;
				}
			}
		});
		threadRadioButtons.add(button);
		button.setSelected(selected);
		return button;
	}	
	
	private JButton createButton(final String the_button) {
		final JButton button = new JButton(the_button);
		button.addActionListener(new ActionListener() {
			public void actionPerformed(final ActionEvent the_event) {
				if (the_button.equalsIgnoreCase("ADD")) {
					addkeyword();
				} else if (the_button.equalsIgnoreCase("CLEAR")) {
					clear_west();
					keywords.clear();
					keywordTF.setText("");
					keywordTF.setEnabled(true);
				} else if (the_button.equalsIgnoreCase("CRAWL")) {
					pageLimit = Integer.parseInt(pageLimitTF.getText());
					url = urlTF.getText().trim();
					try {
						if (multiThreadCrawling)
							crawlMultiThread();
						else
							crawlSingleThread();
					} catch (IOException e) {
						e.printStackTrace();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		});
		return button;
	}
	
	private void crawlSingleThread() throws IOException {
		pa = new PageAnalyzer(keywords);
		pr = new PageRetriever(pa, pageLimit, url);
		pr.addURLStringToQueueToScan(url);
		pr.scanPage();
		
		statsTA.setText("");
		hitsTA.setText("");
		
		statsTA.setText(" + Single-Thread Crawling +" + 
				"\n\n\n Total pages crawled: " + pa.crawledPages + 
				"\n\n Total words found: " + pa.getTotalWordsFound() +
				"\n Total URLs found: " + pa.foundLinks + 
				"\n Total unique/valid URLs found: " + pa.uniqueLinks + 
				"\n\n Avg words per page: " + pa.getAvgWordsPerPage() +
				"\n Avg URLs per page : " + pa.getAvgEmbeddedURLsPerPage() + 
				"\n\n Total running time: " + pa.getTotalRunningTime() + " ms" +
				"\n Avg parse time per page: " + pa.getAvgTimePerPage() + " ms\n\n");
		
		for (int i = 0; i < pa.getKeywords().size(); i++) {
			String keyword = pa.getKeywords().get(i);
			hitsTA.setText(hitsTA.getText() + " Keyword:  " + keyword + 
				"\n      Total Hits: " + pa.getTotalHitsForKeyword(keyword) +
				"\n      Avg hits per page: " + pa.getAvgKeywordHitsPerPage(keyword) + "\n\n");
		}
	}
	
	private void crawlMultiThread() throws IOException, InterruptedException {	
		rpa = new RunnablePageAnalyzer(keywords);
		rpr = new RunnablePageRetriever(rpa, pageLimit, url);
		rpp = new RunnablePageParser(rpr, rpa);
		
		rpr.setRunnableParser(rpp);
		rpr.addURLStringToQueueToScan(url);
		
		Thread rpaThread = new Thread(rpa);
		Thread rprThread = new Thread(rpr);
		Thread rppThread = new Thread(rpp);
		
		rprThread.start();
		rppThread.start();
		rpaThread.start();
		
		while (true) {
			if ((rpa.done() && rpr.done() && rpp.done())) {
				rpr.stop();
				rpp.stop();
				rpa.stop();
				break;
			}
		}
		
		statsTA.setText("");
		hitsTA.setText("");
		
		statsTA.setText(" + Multi-Thread Crawling +" + 
				"\n\n\n Total pages crawled: " + rpa.crawledPages + 
				"\n\n Total words found: " + rpa.getTotalWordsFound() +
				"\n Total URLs found: " + rpa.foundLinks + 
				"\n Total unique/valid URLs found: " + rpa.uniqueLinks + 
				"\n\n Avg words per page: " + rpa.getAvgWordsPerPage() +
				"\n Avg URLs per page : " + rpa.getAvgEmbeddedURLsPerPage() + 
				"\n\n Total running time: " + rpa.getTotalRunningTime() + " ms" +
				"\n Avg parse time per page: " + rpa.getAvgTimePerPage() + " ms\n\n");
		
		for (int i = 0; i < rpa.getKeywords().size(); i++) {
			String keyword = rpa.getKeywords().get(i);
			hitsTA.setText(hitsTA.getText() + " Keyword:  " + keyword + 
				"\n      Total Hits: " + rpa.getTotalHitsForKeyword(keyword) +
				"\n      Avg hits per page: " + rpa.getAvgKeywordHitsPerPage(keyword) + "\n\n");
		}
	}
	
	public static void main(String... args) throws IOException, InterruptedException{
		final WebCrawlerGUI wc = new WebCrawlerGUI();
		wc.start();
	}
}
